import {
    // 第三方依赖
    load,
    he,

    // common中的属性
    UserAgentKey,
    randomUserAgent,
    randomPCUserAgent,
    randomMobileUserAgent,
    htmlToString,
    removeBasedOnSeparator,
    getPathFromUrl,
    checkEncoding,
    delayed,
    timeoutPromise,
    getBaseUrl,
    mySetTimeout,
    myClearTimeout,
    removeQuerySelector,
    Gender,
    GenderEnum,
    removeEmptyLines,
    removeLinesContaining,
    formatString,
    getAbsPath,

    // discover-class
    FilterCategory,
    FilterOption,
    FilterParameter,
    FilterCategoryType,
    DiscoverBook,
    BookLink,
    PageResult,
    DiscoverSource,

    // book-class
    BasicBook,
    Book,
    BasicChapter,
    SearchBookResult,
    BookSource,

    // 爬虫抽象class
    AbstractBookCrawler,
    BookCrawlerTest,
    AbstractDiscoverCrawler,
    DiscoverCrawlerTest,

    // request class
    RequestOptions,
    RequestType,
    RequestMethod,
    DeviceType,
    RequestResponse,
    RequestInputType,
    GetCookiesOptions,
} from './common.js'

import {Builder} from 'selenium-webdriver';
import chrome from "selenium-webdriver/chrome.js";

import iconv from 'iconv-lite'

import readline from 'readline';
import {promises as fs} from 'fs';
import {ProxyAgent} from "undici";

/**
 * 发送请求
 * @param {String} url
 * @param {RequestOptions} options
 * @returns {RequestResponse} 返回response对象
 *
 */
async function request(url, options) {
    options = new RequestOptions(options ?? {});
    console.log("发送请求", url, options);
    const requestType = options.type.toLowerCase();

    if (!['fetch', 'webview'].includes(requestType)) {
        throw new Error(`Unimplemented options.type: ${options.type}`);
    }

    return await timeoutPromise(options.timeout, new Promise(async (resolve, reject) => {
        for (let i = 0; i < options.retryCount; i++) {
            if (i !== 0) { // 只要不是第一次，则延时等待
                await delayed(5 * 1000); // 延时5秒
            }
            try {
                if (requestType === 'fetch') {
                    resolve(await requestByFetch(url, options));
                } else {
                    resolve(await requestByWebView(url, options));
                }
                return;
            } catch (error) {
                if (error instanceof AppError) {
                    throw error;
                }
                console.log(`request error ${i + 1}`, error);
            }
        }
        reject(new Error(`Request error ... url: ${url}, options: ${JSON.stringify(options)}`));
    }));
}

/**
 * 发送fetch请求
 * @param {String} url
 * @param {RequestOptions} options
 * @returns {RequestResponse} 返回response对象
 *
 */
async function requestByFetch(url, options) {

    try {
        let response = await fetch(url, {
            method: options.method,
            headers: options.headers,
            body: options.body,
            signal: AbortSignal.timeout(options.timeout),
            dispatcher: options.proxy ? new ProxyAgent(options.proxy) : undefined,
        });

        let text;
        const decoder = options.decoder;
        if (decoder) {
            checkEncoding(decoder);
            text = async () => iconv.decode(Buffer.from(await response.arrayBuffer()), decoder);
        } else {
            text = response.text.bind(response);
        }

        const headers = {};
        if (response.headers) {
            response.headers.forEach((value, key) => {
                headers[key] = value;
            });
        }
        return new RequestResponse({
            url: response.url,
            status: response.status,
            text: text,
            headers: headers,
        });
    } catch (error) {
        throw new Error(`Error fetching data: ${error.message}`);
    }
}

/**
 * 发送fetch请求
 * @param {String} url
 * @param {RequestOptions} options
 * @returns {RequestResponse} 返回response对象
 *
 */
async function requestByWebView(url, options) {
    let driver;
    try {
        let content = "";
        if (RequestInputType.isFilePath(options.inputType)) {
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout
            });

            // 使用 Promise 包装 rl.question 以便使用 async/await
            const question = (query) => new Promise((resolve) => rl.question(query, resolve));
            try {
                console.log(`需要手动获取网页内容, url: ${url}, options:`, options);
                const filePath = await question('请输入文件路径: ');
                rl.close();
                content = await fs.readFile(filePath, 'utf8');
            } catch (err) {
                throw new AppError(`无法读取文件: ${err.message}`)
            }
        } else if (RequestInputType.none === options.inputType) {
            console.log("打开浏览器")
            let userAgent;
            for (let headersKey in options.headers) {
                if (headersKey.toLowerCase() === 'user-agent') {
                    userAgent = options.headers[headersKey];
                    break;
                }
            }

            let chromeOptions = new chrome.Options();
            chromeOptions.addArguments(`--user-agent=${userAgent}`);

            // 使用Chrome浏览器
            driver = await new Builder()
                .forBrowser('chrome')
                .setChromeOptions(chromeOptions)
                .build();
            await driver.manage().setTimeouts({pageLoad: options.timeout});

            // 设置请求头
            if (options.headers) {
                for (const [header, value] of Object.entries(options.headers)) {
                    if (header.toLowerCase() === 'user-agent') {
                        continue;
                    }
                    await driver.executeScript(`
                 window.navigator.__defineGetter__('${header}', function() {
                     return '${value}';
                 });
             `);
                }
            }

            // 判断是否为post请求
            if (options.method.toLowerCase() === 'post') {

                // 打开基础的url
                await driver.get(getBaseUrl(url));

                // 如果是POST请求，则不考虑请求体，统一使用 baseUrl?query 的方式传参，其它参数方式暂不考虑
                driver.executeScript(`
                var form = document.createElement('form');
                form.style.display = 'none'; // 隐藏表单
                form.method = 'post';
                form.action = '${url}'; // 设置表单的 action 属性
                document.body.appendChild(form);
                form.submit(); // 提交表单
            `);
                // 等待2秒，Form提交完成
                await delayed(2000)
            } else {
                await driver.get(url);
            }

            async function isCloudflareChallenge() {
                return await driver.executeScript("return !!window._cf_chl_opt;");
            }

            // const startTime = Date.now();
            // while (await isCloudflareChallenge() && Date.now() < startTime + 30 * 1000) {
            //     await delayed(1000);
            // }

            let content = await driver.getPageSource();
            if (options.delayedGetContent) {
                await delayed(options.delayedGetContent);
                content = await driver.getPageSource();
            }

            // 判断是否有cloudflare
            if (await isCloudflareChallenge()) {
                throw new AppError("Unsupported Operation Cloudflare Challenge.");
            }
        }
        let cookieString;

        if (options.inputType === RequestInputType.none) { // 说明是浏览器
            const cookies = await driver.manage.cookies();
            cookieString = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ');
        } else if (RequestInputType.isCookies(options.inputType)) {
            cookieString = await getCookies(url);
        } else {
            cookieString = null;
        }

        return new RequestResponse({
            url: driver.url,
            status: 200,
            text: async () => content,
            cookies: async () => cookieString
        });
    } catch (error) {
        if (error instanceof AppError) {
            throw error;
        }
        throw new Error(`Error WebView data: ${error.message}`);
    } finally {
        if (driver) {
            driver.quit();
        }
    }
}

class AppError extends Error {
    constructor(message) {
        super(message); // 调用父类的构造函数
        this.name = this.constructor.name; // 设置错误的名称为类名
        Error.captureStackTrace(this, this.constructor); // 创建堆栈跟踪
    }
}

/**
 * 把字符串转为urlEncode编码
 * @param {*} inputString 需要encodeUrl的字符串
 * @param {*} encoding 编码
 */
function encodeUrl(inputString, encoding) {
    const encodedBuffer = iconv.encode(inputString, encoding);
    let urlEncodedString = '';
    for (const byte of encodedBuffer) {
        urlEncodedString += '%' + byte.toString(16).toUpperCase();
    }
    return urlEncodedString;
}

/**
 *
 * @param {String} url 需要获取Cookies的url
 * @return {Promise<String>} Cookies
 */
async function getCookies(url) {
    console.log(`需要手动获取Cookie, url: ${url}`);
    return inputContent("请输入Cookies:");
}

/**
 * 让用户输入内容
 * @param {String} promptMessage 提示消息
 * @return {Promise<String>} 输入的内容
 */
async function inputContent(promptMessage) {
    promptMessage ??= "请输入内容:";
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    // 使用 Promise 包装 rl.question 以便使用 async/await
    const question = (query) => new Promise((resolve) => rl.question(query, resolve));
    const content = await question(promptMessage);
    rl.close();
    return content;
}

export {
    // 自身的属性
    request,
    encodeUrl,
    getCookies,

    // 第三方依赖
    load,
    he,

    // common中的属性
    UserAgentKey,
    randomUserAgent,
    randomPCUserAgent,
    randomMobileUserAgent,
    htmlToString,
    removeBasedOnSeparator,
    getPathFromUrl,
    checkEncoding,
    delayed,
    timeoutPromise,
    getBaseUrl,
    mySetTimeout,
    myClearTimeout,
    removeQuerySelector,
    Gender,
    GenderEnum,
    removeEmptyLines,
    removeLinesContaining,
    formatString,
    getAbsPath,

    // discover-class
    FilterCategory,
    FilterOption,
    FilterParameter,
    FilterCategoryType,
    DiscoverBook,
    BookLink,
    PageResult,
    DiscoverSource,

    // book-class
    BasicBook,
    Book,
    BasicChapter,
    SearchBookResult,
    BookSource,

    // 爬虫抽象class
    AbstractBookCrawler,
    BookCrawlerTest,
    AbstractDiscoverCrawler,
    DiscoverCrawlerTest,

    // request class
    RequestOptions,
    RequestType,
    RequestMethod,
    DeviceType,
    RequestResponse,
    RequestInputType,
    GetCookiesOptions,
};
